package com.otaku.admin.filter;


import com.otaku.admin.dao.entity.SelfUserDetails;
import com.otaku.admin.dao.entity.Token;
import com.otaku.admin.service.RedisTemplateService;
import com.otaku.admin.service.SelfUserDetailsService;
import com.otaku.admin.service.TokenService;
import com.otaku.admin.util.JwtTokenUtil;
import com.otaku.admin.util.NumericTypeConstant;
import com.otaku.admin.util.StringTypeConstant;
import com.otaku.common.util.DateUtils;
import io.jsonwebtoken.ExpiredJwtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: zzx
 * @date: 2018/10/15 17:30
 * @description: 确保在一次请求只通过一次filter，而不需要重复执行
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
    @Autowired
    private SelfUserDetailsService userDetailsService;
    @Autowired
    private RedisTemplateService redisTemplateService;
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String authToken = null;
            if (authHeader.startsWith("Bearer ")){
                authToken = authHeader.substring("Bearer ".length());
            } else {
                authToken = authHeader;
            }
            String username = null;
            try {
                /**
                 * 修改权限策略
                 */
                username = JwtTokenUtil.parseTokenStr(authToken);
                /**
                 * 验证authToken是否失效
                 */
                String redisToken = redisTemplateService.get(StringTypeConstant.getRedisTokenTitles(username), String.class);
                /**
                 * 1.当redisToken is null || 与 authToken 不相同时
                 * 2.需要验证数据库中保存的 authToken是否有效
                 * 3.当数据库中保存的 token是否有效
                 */
                if (StringUtils.isEmpty(redisToken)||!authToken.equals(redisToken)) {//token 验证未通过情况
                    Token tokenDB = tokenService.findByAccessToken(authToken,NumericTypeConstant.TOKEN_STATUS_SUCCESS);
                    if (tokenDB != null && (tokenDB.getRefreshTimeOut().getTime() - DateUtils.getNowTimeStampTime().getTime() > 0)) {
                        redisTemplateService.set(StringTypeConstant.getRedisTokenTitles(username), NumericTypeConstant.REDIS_TOKEN_TIME_OUT * 60L, authToken);
                    } else {
                        //当数据库中数据 无效或过期时，判定该token为无效
                        if (tokenDB != null){
                            tokenDB.setStatus(NumericTypeConstant.TOKEN_STATUS_INVALID);
                            tokenService.updateToken(tokenDB);
                        }
                        username = null;
                    }
                }//token验证通过

            } catch (Exception e) {
                //权限失效后，获取数据库中已保存的refreshToken
                LOGGER.info("验证token异常：{}", e.getMessage());
                Token tokenDB = tokenService.findByAccessToken(authToken,NumericTypeConstant.TOKEN_STATUS_SUCCESS);
                if (tokenDB != null && (tokenDB.getRefreshTimeOut().getTime() - DateUtils.getNowTimeStampTime().getTime() > 0)) {
                    /**
                     * accessToken已失效，refreshToken未失效情况下，重新生成accessToken
                     */
                    Integer userId = tokenDB.getUserId();
                    SelfUserDetails userDetails = userDetailsService.findByUserId(userId);
                    if (userDetails != null) {
                        authToken = JwtTokenUtil.generateToken(userDetails, NumericTypeConstant.ACCESS_TOKEN_TIME_OUT * 1000L);//分钟
                        tokenDB.setAccessToken(authToken);
                        tokenService.updateToken(tokenDB);
                        redisTemplateService.set(StringTypeConstant.getRedisTokenTitles(username), NumericTypeConstant.REDIS_TOKEN_TIME_OUT * 60L, authToken);
                        request.setAttribute(StringTypeConstant.REFRESH_TOKEN_REQUEST,authToken);
                    }
                } else {
                    //过期，失效token
                    if (tokenDB != null){
                        tokenDB.setStatus(NumericTypeConstant.TOKEN_STATUS_INVALID);
                        tokenService.updateToken(tokenDB);
                    }
                }
                username = null;
            }
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}
